/*
 * Console.java
 *
 * Created on 24 octobre 2007, 11:13
 */
package com.reimscopie.console ;

import java.io.* ;
import java.util.* ;

import javax.naming.* ;


/**
 * This is the main class of the console client.
 */
public class Console {
  
  // input and output streams
  private BufferedReader in ;
  private PrintWriter out ;
  private PrintWriter err ;
  
  // the console uses an interactive terminal
  private boolean term ;
  
  // commands, mapped on names
  private Map<String,Command> commands ;
  
  // variables, mapped on names
  private Map<String,Object> variables ;
  
  
  /**
   * Creates a new instance of <code>Console</code> with the specified input
   * and output streams.
   *
   * @param in The default input characters stream.
   * @param out The default output characters stream.
   * @param err The default error characters stream.
   * @param term The console is based on a terminal.
   */
  public Console(Reader in,PrintWriter out,PrintWriter err,boolean term) {
    
    this.out  = out ;
    this.err  = err ;
    this.term = term ;

    this.in = new BufferedReader(in) ;

    commands  = new TreeMap<String,Command>() ;
    variables = new TreeMap<String,Object>() ;
  }


  /**
   * Add a new command to this shell. The command can be invoked by its name
   *
   * @param command The command to add.
   */
  public void mapCommand(Command command) {
  
    String name = command.getName() ;
    commands.put(name,command) ;
    
    command.setVariableMap(variables) ;
    
  }
  

  /**
   * Start the console. All commands are executed.
   */
  public void start() {

    if (term)
      printBanner() ;
    
    while (true) {
      
      if (term) {
        out.print("$ ") ;
        out.flush() ;
      }
      
      // command line input
      String line = readLine() ;
      if (line == null)
        return ;
      
      // tokens
      String[] tokens = tokenizeCommand(line) ;
      if (tokens == null)
        continue ;
      
      // special comment
      if (tokens[0].charAt(0) == '#')
        continue ;
      
      // special quit command
      if (tokens[0].equals("quit") || tokens[0].equals("exit"))
        return ;
      
      // general execution
      execute(tokens) ;
    }
    
  }
  
  
  /**
   * Print the banner when the console is started.
   */
  private void printBanner() {

    out.println("Ecom terminal") ;
    out.println("(C) 2007 - Reims Copie") ;
    
  }


  /**
   * Read a new command from the input.
   *
   * @return The command line, ou <code>null</code> if no command can be
   * read.
   */
  private String readLine() {

    try {
      return in.readLine() ;
    }
    catch (IOException e) {
      err.println("Could not read input (I/O error).") ;
      return null ;
    }
    
  }


  /**
   * Split a command line into separate words.
   *
   * @param line The whole command line.
   * 
   * @return The tokens from the command, or <code>null</code> if the
   * command is empty.
   */
  private String[] tokenizeCommand(String line) {
    
    try {
      StringReader reader = new StringReader(line) ;
      CommandTokenizer tokenizer = new CommandTokenizer(reader) ;
      List<String> list = new ArrayList<String>() ;
      
      while (tokenizer.hasMoreTokens()) {
        String token = tokenizer.nextToken() ;
        list.add(token) ;
      }
      
      // conversion to array
      if (list.isEmpty())
        return null ;
      else {
        String[] array = new String[list.size()] ;
        list.toArray(array) ;
        return array ;
      }
    }
    catch (IOException e) {
      e.printStackTrace(err) ;
      return null ;
    }
  }


  /**
   * Execute the specified command.
   *
   * @param tokens The command tokens.
   */
  private void execute(String[] tokens) {
  
    String name = tokens[0] ;
  
    Command command = commands.get(name) ;
    
    // no such command
    if (command == null) {
      err.println("no such command: "+name) ;
    }

    // execute
    else {
      String[] args ;
      if (tokens.length == 1)
        args = null ;
      else {
        args = new String[tokens.length-1] ;
        System.arraycopy(tokens,1,args,0,args.length) ;
      }
      
      ConsoleOutput output = new ConsoleOutput(out,err) ;
      
      try {
        command.execute(args,output) ;
      }
      catch (CommandException e) {
        traceError(output,e) ;
        output.printError("Command failed !",true) ;
      }
    }
    
  }
  
  
  /**
   * Trace an exception in the console output. The exception is printed in
   * the standard error output.
   *
   * @param output The console output.
   * @param th The error to trace.
   */
  private void traceError(ConsoleOutput output,Throwable th) {
    
    if (trace)
      output.printError(th) ;
    
    else {
      do {
        String message = th.getMessage() ;
        output.printError(message,true) ;
        th = th.getCause() ;
      }
      while (th != null) ;
    }
    
  }
  
  
///////////////////////////////////////////////////////////////////////////////
//                                MAIN                                       //
///////////////////////////////////////////////////////////////////////////////
  
  // verbose level
  private static boolean trace ;
  
  
  /**
   * This main method starts the console.
   *
   * @param args The command line arguments.
   */
  public static void main(String[] args) {
    
    parseOptions(args) ;
    
    initContext() ;
    
    Console console = createConsole(args) ;
    
    // map default commands
    console.mapCommand(new Help(console.commands)) ;
    console.mapCommand(new Quit()) ;
    console.mapCommand(new Set()) ;
    
    mapCommands(console) ;
    
    console.start() ;
  }
  
  
  /**
   * Parse options arguments.
   *
   * @param args All command arguments.
   */
  private static void parseOptions(String[] args) {
    
    for (String arg : args) {
      
      if (arg.equals("-trace"))
        trace = true ;
    }
    
  }
  
  
  /**
   * Map all user commands in the console.
   *
   * @param console The newly created console.
   */
  private static void mapCommands(Console console) {
    
    // user session
    console.mapCommand(new Login()) ;
    
    // account
    console.mapCommand(new CreateAccount()) ;
    console.mapCommand(new GetAccount()) ;
    
    // product
    console.mapCommand(new CreateSimpleProduct()) ;
    console.mapCommand(new CreateSpecialProduct()) ;
    console.mapCommand(new GetProduct()) ;
    console.mapCommand(new BuySimpleProduct()) ;
    console.mapCommand(new Search()) ;
    
    // bon plan
    console.mapCommand(new CreateBonPlan()) ;
    
    // comment
    console.mapCommand(new CreateComment()) ;
    
    // orders
    console.mapCommand(new GetOrders()) ;
    
    // cart
    console.mapCommand(new GetCart()) ;
  }
  
  
  /**
   * Creates the console instance.
   *
   * @param args The command line arguments.
   *
   * @return A new console instance. 
   */
  private static Console createConsole(String[] args) {
    
    // outputs
    PrintWriter out = new PrintWriter(System.out,true) ;
    PrintWriter err = new PrintWriter(System.err,true) ;
    
    int inputIndex = -1 ;
    for (int i=0;i<args.length;i++) {
      if (!args[i].startsWith("-")) {
        inputIndex = i ;
        break ;
      }
    }
    
    // terminal input
    if (inputIndex == -1) {
      Reader in = new InputStreamReader(System.in) ;
      return new Console(in,out,err,true) ;
    }
    // file input
    else {
      try {
        FileReader reader = new FileReader(args[inputIndex]) ;
        return new Console(reader,out,err,false) ;
      }
      catch (FileNotFoundException e) {
        System.err.print("Console initialization failed: ") ;
        System.err.print(e.getMessage()) ;
        System.err.println() ;
        System.exit(1) ;
        return null ;
      }
    }
    
  }
  
  
  /**
   * Initialize the running context.
   */
  private static void initContext() {

    // JNDI context
    System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
        "org.objectweb.carol.jndi.spi.MultiOrbInitialContextFactory") ;
    
  }
  
}
